package io.itit.ecp.admin.provide.action.controller.miniprogram;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import icu.mhb.mybatisplus.plugln.extend.Joins;
import io.itit.ecp.admin.common.enums.CarbonQuotaInputTypeEnums;
import io.itit.ecp.admin.common.enums.CarbonRevenueForecastDataRevenueTypeEnums;
import io.itit.ecp.admin.common.enums.IMeterDeviceEnums;
import io.itit.ecp.admin.provide.action.resp.CarbonCCERStaticsResp;
import io.itit.ecp.admin.provide.action.resp.largescreen.CarbonAssetStatisticsResp;
import io.itit.ecp.admin.provide.action.resp.largescreen.CarbonPriceOrccerResp;
import io.itit.ecp.admin.provide.action.resp.largescreen.CarbonReductionStatisticsResp;
import io.itit.ecp.admin.provide.action.resp.largescreen.CarbonRevenueForecastResp;
import io.itit.ecp.admin.server.entity.*;
import io.itit.ecp.admin.server.service.ICarbonCCERService;
import io.itit.ecp.admin.server.service.ICarbonPriceDataService;
import io.itit.ecp.admin.server.service.ICarbonQuotaService;
import io.itit.ecp.admin.server.service.ICarbonRevenueForecastDataService;
import io.itit.grass.common.provide.action.resp.ResultResp;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 碳 资 产
 */
@Api(tags = {"碳资产"})
@RestController
@RequiredArgsConstructor
@RequestMapping("/mini/carbonAsset")
public class MiniCarbonAssetController {

    @Resource
    private ICarbonQuotaService carbonQuotaService;

    @Resource
    private ICarbonCCERService carbonCCERService;

    @Resource
    private ICarbonPriceDataService carbonPriceDataService;

    @Resource
    private ICarbonRevenueForecastDataService carbonRevenueForecastDataService;

    @ApiOperation("碳资产统计")
    @GetMapping("carbonassetstatistics")
    public ResultResp<CarbonAssetStatisticsResp> carbonAssetStatistics(@RequestParam(required = false) String sceneProjectId,
                                                                       @RequestParam(required = false) String sceneEdificeId,
                                                                       @RequestParam(required = false) String sceneFloorId,
                                                                       @RequestParam(required = false) String startTime,
                                                                       @RequestParam(required = false) String endTime) {
        LocalDateTime start = StringUtils.hasLength(startTime) ? LocalDate.parse(startTime, DateTimeFormatter.ofPattern("yyyy-MM-dd")).atTime(LocalTime.MIN) : null;
        LocalDateTime end = StringUtils.hasLength(endTime) ? LocalDate.parse(endTime, DateTimeFormatter.ofPattern("yyyy-MM-dd")).atTime(LocalTime.MAX) : LocalDateTime.now();

        List<CarbonQuotaEntity> quotaEntities = carbonQuotaService.list(new LambdaQueryWrapper<CarbonQuotaEntity>()
                .eq(StringUtils.hasLength(sceneProjectId), CarbonQuotaEntity::getSceneProjectId, sceneProjectId)
                .eq(StringUtils.hasLength(sceneEdificeId), CarbonQuotaEntity::getSceneEdificeId, sceneEdificeId)
                .eq(StringUtils.hasLength(sceneFloorId), CarbonQuotaEntity::getSceneFloorId, sceneFloorId)
                .ge(Objects.nonNull(start), CarbonQuotaEntity::getCreateTm, start)
                .le(CarbonQuotaEntity::getCreateTm, end));


        List<CarbonCCEREntity> carbonCCEREntities = carbonCCERService.list(new LambdaQueryWrapper<CarbonCCEREntity>()
                .eq(StringUtils.hasLength(sceneProjectId), CarbonCCEREntity::getSceneProjectId, sceneProjectId)
                .eq(StringUtils.hasLength(sceneEdificeId), CarbonCCEREntity::getSceneEdificeId, sceneEdificeId)
                .eq(StringUtils.hasLength(sceneFloorId), CarbonCCEREntity::getSceneFloorId, sceneFloorId)
                .ge(Objects.nonNull(start), CarbonCCEREntity::getCreateTm, start)
                .le(CarbonCCEREntity::getCreateTm, end));

        CarbonAssetStatisticsResp statisticsResp = new CarbonAssetStatisticsResp();
        CarbonCCERStaticsResp ccerStaticsResp = ccerStatics(carbonCCEREntities, staticsCarbonEmissionOverview(sceneProjectId, sceneEdificeId, sceneFloorId, start, end));
        statisticsResp.setTotal(remainingCarbonQuota(quotaEntities, getUsedValue(sceneProjectId, sceneEdificeId, sceneFloorId, start, end))
                .add(ccerStaticsResp.getRemainingValue()).setScale(2, RoundingMode.HALF_DOWN));
        statisticsResp.setTotalSel(ccerStaticsResp.getTotalValue().setScale(2, RoundingMode.HALF_DOWN));
        statisticsResp.setTotalEnd(ccerStaticsResp.getSaleValue().setScale(2, RoundingMode.HALF_DOWN));
        return ResultResp.success(statisticsResp);
    }


    /**
     * 碳收益预测
     *
     * @param sceneProjectId sceneProjectId
     * @param sceneEdificeId sceneEdificeId
     * @param sceneFloorId   sceneFloorId
     * @param startTime      startTime
     * @param endTime        endTime
     */
    @ApiOperation("碳收益预测")
    @GetMapping("carbonRevenueForecast")
    public ResultResp<List<CarbonRevenueForecastResp>> carbonRevenueForecast(@RequestParam(required = false) String sceneProjectId,
                                                                             @RequestParam(required = false) String sceneEdificeId,
                                                                             @RequestParam(required = false) String sceneFloorId,
                                                                             @RequestParam(required = false) String startTime,
                                                                             @RequestParam(required = false) String endTime) {
        LocalDateTime start = StringUtils.hasLength(startTime) ? LocalDate.parse(startTime, DateTimeFormatter.ofPattern("yyyy-MM-dd")).atTime(LocalTime.MIN) : null;
        LocalDateTime end = StringUtils.hasLength(endTime) ? LocalDate.parse(endTime, DateTimeFormatter.ofPattern("yyyy-MM-dd")).atTime(LocalTime.MAX) : LocalDateTime.now();
        List<CarbonRevenueForecastResp> resps = new ArrayList<>();
//        // 管理后台-数据管理-碳收益预测数据-碳配额/CCER收益预测值
        List<CarbonRevenueForecastDataEntity> forecastDataEntities = carbonRevenueForecastDataService
                .queryForList(sceneProjectId, sceneEdificeId, sceneFloorId, null, start, end);
        Map<String, List<CarbonRevenueForecastDataEntity>> solidifyDateMap = forecastDataEntities.stream()
                .collect(Collectors.groupingBy(item -> DateUtil.format(item.getSolidifyDate(), "yyyy-MM"), TreeMap::new, Collectors.toList()));
        List<CarbonReductionStatisticsResp.DataItem> dataItems = new ArrayList<>();
        List<CarbonReductionStatisticsResp.DataItem> ccerDataItems = new ArrayList<>();
        for (Map.Entry<String, List<CarbonRevenueForecastDataEntity>> entry : solidifyDateMap.entrySet()) {
            BigDecimal value = entry.getValue().stream()
                    .filter(item -> CarbonRevenueForecastDataRevenueTypeEnums.CARBON_QUOTA.getValue().equals(item.getRevenueType())
                            && Objects.nonNull(item.getForecastValue()))
                    .map(CarbonRevenueForecastDataEntity::getForecastValue)
                    .reduce(BigDecimal.ZERO, BigDecimal::add)
                    .setScale(2, RoundingMode.HALF_DOWN);
            dataItems.add(new CarbonReductionStatisticsResp.DataItem().setTime(entry.getKey()).setValue(value));

            BigDecimal ccerValue = entry.getValue().stream()
                    .filter(item -> CarbonRevenueForecastDataRevenueTypeEnums.CCER.getValue().equals(item.getRevenueType())
                            && Objects.nonNull(item.getForecastValue()))
                    .map(CarbonRevenueForecastDataEntity::getForecastValue)
                    .reduce(BigDecimal.ZERO, BigDecimal::add)
                    .setScale(2, RoundingMode.HALF_DOWN);
            dataItems.add(new CarbonReductionStatisticsResp.DataItem().setTime(entry.getKey()).setValue(value));
            ccerDataItems.add(new CarbonReductionStatisticsResp.DataItem().setTime(entry.getKey()).setValue(ccerValue));
        }
        resps.add(new CarbonRevenueForecastResp().setName("碳配额").setData(dataItems));
        resps.add(new CarbonRevenueForecastResp().setName("CCER").setData(ccerDataItems));
        return ResultResp.success(resps);
    }


    /**
     * 碳价/CCER价格
     *
     * @param sceneProjectId sceneProjectId
     * @param startTime      startTime
     * @param endTime        endTime
     */
    @ApiOperation("碳价/CCER价格")
    @GetMapping("carbonPriceOrccer")
    public ResultResp<List<CarbonPriceOrccerResp>> carbonPriceOrCcer(@RequestParam(required = false) String sceneProjectId,
                                                                     @RequestParam(required = false) String startTime,
                                                                     @RequestParam(required = false) String endTime) {
        LocalDateTime start = StringUtils.hasLength(startTime) ? LocalDate.parse(startTime, DateTimeFormatter.ofPattern("yyyy-MM-dd")).atTime(LocalTime.MIN) : null;
        LocalDateTime end = StringUtils.hasLength(endTime) ? LocalDate.parse(endTime, DateTimeFormatter.ofPattern("yyyy-MM-dd")).atTime(LocalTime.MAX) : LocalDateTime.now();
        List<CarbonPriceOrccerResp> resps = new ArrayList<>();
        List<CarbonPriceDataEntity> priceDataEntities = carbonPriceDataService.list(new LambdaQueryWrapper<CarbonPriceDataEntity>()
                .eq(StringUtils.hasLength(sceneProjectId), CarbonPriceDataEntity::getSceneProjectId, sceneProjectId)
                .ge(Objects.nonNull(start), CarbonPriceDataEntity::getDataTime, start)
                .le(CarbonPriceDataEntity::getDataTime, end)
                .orderByAsc(CarbonPriceDataEntity::getDataTime)
        );
        Map<String, List<CarbonPriceDataEntity>> dataMap = priceDataEntities.stream().collect(Collectors.groupingBy(item -> item.getDataTime()
                .format(DateTimeFormatter.ofPattern("yyyy-MM-dd")), TreeMap::new, Collectors.toList()));
        CarbonPriceOrccerResp priceResp = new CarbonPriceOrccerResp().setName("碳配额");
        List<CarbonPriceOrccerResp.DataItem> priceDataList = new ArrayList<>();
        CarbonPriceOrccerResp ccerResp = new CarbonPriceOrccerResp().setName("CCER");
        List<CarbonPriceOrccerResp.DataItem> ccerDataList = new ArrayList<>();
        for (Map.Entry<String, List<CarbonPriceDataEntity>> entry : dataMap.entrySet()) {
            String date = entry.getKey();
            String time = Integer.valueOf(StringUtils.tokenizeToStringArray(date, "-")[2]) + "日"; // 2024-12-10 --> 10
            CarbonPriceDataEntity priceDataEntity = entry.getValue().stream().max(Comparator.comparing(CarbonPriceDataEntity::getDataTime)).get();
            priceDataList.add(new CarbonPriceOrccerResp.DataItem().setFullDate(date).setTime(time).setValue(priceDataEntity.getCarbonQuotaPrice().setScale(2, RoundingMode.HALF_DOWN)));
            ccerDataList.add(new CarbonPriceOrccerResp.DataItem().setFullDate(date).setTime(time).setValue(priceDataEntity.getCcerPrice().setScale(2, RoundingMode.HALF_DOWN)));
        }
        priceResp.setData(priceDataList);
        ccerResp.setData(ccerDataList);
        resps.add(priceResp);
        resps.add(ccerResp);
        return ResultResp.success(resps);
    }

    private CarbonCCERStaticsResp ccerStatics(List<CarbonCCEREntity> ccerEntities, BigDecimal reductionValue) {
        CarbonCCERStaticsResp staticsResp = new CarbonCCERStaticsResp();
        Map<String, List<CarbonCCEREntity>> inputTypeMap = ccerEntities.stream().collect(Collectors.groupingBy(CarbonCCEREntity::getInputType));
        staticsResp.setPurchasedValue(inputTypeMap.getOrDefault(CarbonQuotaInputTypeEnums.PURCHASE.getValue(), Collections.emptyList())
                .stream().map(CarbonCCEREntity::getCcerValue).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, RoundingMode.HALF_DOWN));
        staticsResp.setTotalValue(reductionValue.add(staticsResp.getPurchasedValue()).setScale(2, RoundingMode.HALF_DOWN));
        staticsResp.setSaleValue(inputTypeMap.getOrDefault(CarbonQuotaInputTypeEnums.SOLD.getValue(), Collections.emptyList())
                .stream().map(CarbonCCEREntity::getCcerValue).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, RoundingMode.HALF_DOWN));
        staticsResp.setRemainingValue(staticsResp.getTotalValue()
                .subtract(staticsResp.getSaleValue()).setScale(2, RoundingMode.HALF_DOWN));
        return staticsResp;
    }

    private BigDecimal remainingCarbonQuota(List<CarbonQuotaEntity> quotaEntities, BigDecimal usedValue) {
        Map<String, List<CarbonQuotaEntity>> inputTypeMap = quotaEntities.stream().collect(Collectors.groupingBy(CarbonQuotaEntity::getInputType));
        BigDecimal totalValue = inputTypeMap.getOrDefault(CarbonQuotaInputTypeEnums.ADD.getValue(), Collections.emptyList())
                .stream().map(CarbonQuotaEntity::getQuotaValue).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, RoundingMode.HALF_DOWN);
        BigDecimal saleValue = inputTypeMap.getOrDefault(CarbonQuotaInputTypeEnums.SOLD.getValue(), Collections.emptyList())
                .stream().map(CarbonQuotaEntity::getQuotaValue).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, RoundingMode.HALF_DOWN);
        BigDecimal purchasedValue = inputTypeMap.getOrDefault(CarbonQuotaInputTypeEnums.PURCHASE.getValue(), Collections.emptyList())
                .stream().map(CarbonQuotaEntity::getQuotaValue).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, RoundingMode.HALF_DOWN);
        return totalValue.subtract(usedValue).add(purchasedValue).subtract(saleValue);
    }


    private BigDecimal getUsedValue(String sceneProjectId, String sceneEdificeId, String sceneFloorId, LocalDateTime start, LocalDateTime end) {
        BigDecimal res = Joins.of(EnergyDataEntity.class)
                .selectAs(builder -> builder
                        .add("SUM(carbon_emission)", "carbonEmission", false)
                )
                .leftJoin(MeterDeviceEntity.class, MeterDeviceEntity::getId, EnergyDataEntity::getMeterDeviceId)
                .notIn(MeterDeviceEntity::getReadMeterType, IMeterDeviceEnums.ReadMeterType.getZeroCarbonList())
                .end()
                .eq(StringUtils.hasLength(sceneProjectId), EnergyDataEntity::getSceneProjectId, sceneProjectId)
                .eq(StringUtils.hasLength(sceneEdificeId), EnergyDataEntity::getEnergyTypeId, sceneEdificeId)
                .eq(StringUtils.hasLength(sceneFloorId), EnergyDataEntity::getSceneEdificeId, sceneFloorId)
                .ge(Objects.nonNull(start), EnergyDataEntity::getSolidifyDate, start)
                .le(Objects.nonNull(end), EnergyDataEntity::getSolidifyDate, end)
                .joinGetOne(BigDecimal.class);
        return Objects.isNull(res) ? BigDecimal.ZERO : res.setScale(2, RoundingMode.HALF_DOWN);
    }

    private BigDecimal staticsCarbonEmissionOverview(String sceneProjectId,
                                                     String sceneEdificeId,
                                                     String sceneFloorId, LocalDateTime startTime, LocalDateTime endTime) {
        BigDecimal data = Joins.of(EnergyDataEntity.class)
                .selectAs(consumer -> consumer
                        .add("SUM(carbon_emission)", "carbonEmission", false)
                )
                .leftJoin(MeterDeviceEntity.class, MeterDeviceEntity::getId, EnergyDataEntity::getMeterDeviceId)
                .notIn(MeterDeviceEntity::getReadMeterType, IMeterDeviceEnums.ReadMeterType.getZeroCarbonList())
                .end()
                .eq(StringUtils.hasLength(sceneProjectId), EnergyDataEntity::getSceneProjectId, sceneProjectId)
                .eq(StringUtils.hasLength(sceneEdificeId), EnergyDataEntity::getSceneEdificeId, sceneEdificeId)
                .eq(StringUtils.hasLength(sceneFloorId), EnergyDataEntity::getSceneFloorId, sceneFloorId)
                .ge(Objects.nonNull(startTime), EnergyDataEntity::getSolidifyDate, startTime)
                .le(Objects.nonNull(endTime), EnergyDataEntity::getSolidifyDate, endTime)
                .joinGetOne(BigDecimal.class);
        return ObjectUtil.defaultIfNull(data, BigDecimal.ZERO).setScale(2, RoundingMode.HALF_DOWN);
    }
}
